Skip to content

Allowing stateful service loaders - #1616

Merged
fjtirado merged 1 commit into
open-workflow-specification:mainfrom
fjtirado:Fixing_service_loader
Aug 12, 2026
Merged

Allowing stateful service loaders#1616
fjtirado merged 1 commit into
open-workflow-specification:mainfrom
fjtirado:Fixing_service_loader

Conversation

@fjtirado

Copy link
Copy Markdown
Collaborator

Service loaders might be stateful, so caching them might be an isssue. Since internally serviceloader class already caches the classpath search, the right solution is to store a reference to the ServiceLoader itself and call stream for every invocation.

That way, we have the best of both world, fresh new instance of the service loader class for every invocation (supporting stateful) and avoid the classpath search for every invocation (the original performance issue to be fixed)

Copilot AI lite review requested due to automatic review settings August 12, 2026 07:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR changes how service-provider implementations are resolved, aiming to avoid caching provider instances while still avoiding repeated classpath scanning by caching ServiceLoader objects and re-streaming them per invocation.

Changes:

  • Replaced caching of loaded service instances with caching of ServiceLoader<?> in WorkflowApplication.
  • Updated serviceLoadedClasses(...) / serviceLoadedClass(...) to re-stream providers on each call.
  • Updated DefaultTaskExecutorFactory to resolve CallableTaskBuilder implementations via WorkflowApplication instead of its own cached ServiceLoader result.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java Switches service resolution cache from instance lists to cached ServiceLoader and re-streaming per call.
impl/core/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java Routes CallableTaskBuilder discovery through WorkflowApplication service-loading helpers.
Suppressed comments (1)

impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java:724

  • serviceLoadedClass now returns the first provider in ServiceLoader iteration order, which is not guaranteed to respect ServicePriority/Comparable ordering. This can cause a lower-priority implementation to be selected.
    ServiceLoader<?> serviceLoader =
        servicesLoaded.computeIfAbsent(serviceClass, ServiceLoader::load);
    return (T)
        serviceLoader.stream()
            .map(ServiceLoader.Provider::get)

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java Outdated
Service loaders might be stateful, so caching them might be an isssue.
Since internally serviceloader class already caches the classpath
search, the right solution is to store a reference to the ServiceLoader
itself and call stream for every invocation.

That way, we have the best of both world, fresh new instance of the
service loader class for every invocation (supporting stateful) and
avoid the classpath search for every invocation (the original
performance issue to be fixed)

Signed-off-by: Francisco Javier Tirado Sarti <ftirados@ibm.com>
Copilot AI review requested due to automatic review settings August 12, 2026 08:19
@fjtirado
fjtirado force-pushed the Fixing_service_loader branch from f0ff8be to 4bc755e Compare August 12, 2026 08:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (2)

impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java:716

  • ServiceLoader instances are not documented as thread-safe. Caching a single ServiceLoader in servicesLoaded and then calling stream() on it from multiple threads can lead to racy provider discovery/iteration. Consider synchronizing access per cached loader (or otherwise ensuring single-threaded use) and tighten the generics to avoid the wildcard/unchecked List cast.
  public <T extends Comparable<?>> List<T> serviceLoadedClasses(Class<T> clazz) {
    ServiceLoader<?> serviceLoader = servicesLoaded.computeIfAbsent(clazz, ServiceLoader::load);
    return (List<T>) serviceLoader.stream().map(ServiceLoader.Provider::get).sorted().toList();

impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java:727

  • Using sorted().findFirst() forces a full sort (O(n log n)) even though only the minimum element is needed. Prefer min(Comparator.naturalOrder()) to select the highest-priority implementation without sorting the entire stream (and keep access synchronized if the cached ServiceLoader can be used concurrently).
        serviceLoader.stream()
            .map(ServiceLoader.Provider::get)
            .sorted()
            .findFirst()
            .orElseThrow(

@fjtirado
fjtirado merged commit 606f10b into open-workflow-specification:main Aug 12, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants